package com.cp.springsecurityauthor;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.GroupManager;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;

import javax.sql.DataSource;
import javax.swing.text.rtf.RTFEditorKit;
import java.io.PrintWriter;
import java.util.List;

/**
 * @author ChengPeng
 * @create 2020-04-24 16:02
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    DataSource dataSource;
    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    //角色继承,在配置时，需要给角色手动加上 ROLE_ 前缀
    @Bean
    RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
        //ROLE_admin 自动具备 ROLE_user 的权限。
        hierarchy.setHierarchy("ROLE_admin > ROLE_user");
        return hierarchy;
    }

    //基于内存配置测试用户
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.inMemoryAuthentication()
//                .withUser("javaboy")
//                .password("123").roles("admin")
//                .and()
//                .withUser("江南一点雨")
//                .password("123")
//                .roles("user");
//    }


    //Spring Security 支持多种数据源，例如内存、数据库、LDAP 等
    // 重写 WebSecurityConfigurerAdapter 中的 userDetailsService 方法来提供一个 UserDetailService 实例进而配置多个用户：
    /*@Override
    @Bean
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("cphsw").password("123").roles("admin").build());
        manager.createUser(User.withUsername("user").password("123").roles("user").build());
        return manager;
    }*/
    @Override
    @Bean
    protected UserDetailsService userDetailsService(){
        //UserDetailsService 实现类中，除了 InMemoryUserDetailsManager 之外，还有一个 JdbcUserDetailsManager，
        // 使用 JdbcUserDetailsManager 可以让我们通过 JDBC 的方式将数据库和 Spring Security 连接起来
        JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
        manager.setDataSource(dataSource);
        //调用 userExists 方法判断用户是否存在,因为每次项目启动时这段代码都会执行，所以加一个判断，避免重复创建用户
        //这里的 createUser 或者 userExists 方法其实都是调用写好的 SQL 去判断的，我们从它的源码里能看出来
       /* public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsManager,
                GroupManager {
            public static final String DEF_USER_EXISTS_SQL = "select username from users where username = ?";
            private String userExistsSql = DEF_USER_EXISTS_SQL;
            public boolean userExists(String username) {
                List<String> users = getJdbcTemplate().queryForList(userExistsSql,
                        new String[] { username }, String.class);
                if (users.size() > 1) {
                    throw new IncorrectResultSizeDataAccessException(
                            "More than one user found with name '" + username + "'", 1);
                }
                return users.size() == 1;
            }
        }*/
        if(!manager.userExists("cphsw")){
            manager.createUser(User.withUsername("cphsw").password("123").roles("admin").build());
        }
        if(!manager.userExists("user")){
            manager.createUser(User.withUsername("user").password("123").roles("user").build());
        }
        return manager;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //匹配规则采用了 Ant 风格的路径匹配符
                //**	匹配多层路径   *	匹配一层路径   ?	匹配任意单个字符
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/user/**").hasRole("user")
                //anyRequest必须在antMatchers后面，anyRequest 应该放在最后，表示除了前面拦截规则之外，剩下的请求要如何处理。
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/doLogin")
                .successHandler((req, resp, authentication) -> {
                    Object principal = authentication.getPrincipal();
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write(new ObjectMapper().writeValueAsString(principal));
                    out.flush();
                    out.close();
                })
                .failureHandler((req, resp, e) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write(e.getMessage());
                    out.flush();
                    out.close();
                })
                .permitAll()
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler((req, resp, authentication) -> {
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    out.write("注销成功");
                    out.flush();
                    out.close();
                })
                .permitAll()
                .and()
                .csrf().disable().exceptionHandling()
                .authenticationEntryPoint((req, resp, authException) -> {
                            resp.setContentType("application/json;charset=utf-8");
                            PrintWriter out = resp.getWriter();
                            out.write("尚未登录，请先登录");
                            out.flush();
                            out.close();
                        }
                );
    }
}
